/*
 *   This File is Part of CuteOgre Game Engine
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *	Copyright 2010 Alexandre Pires ( c.alexandre.pires@gmail.com )
 *  Created on: 2010/08/22
 */


#include <QDebug>

#include "SoundManager.h"
#include "ConfigManager.h"

template<> SoundManager* Singleton<SoundManager>::ms_Singleton = 0;

SoundManager::SoundManager(QObject* rParent) :
	QObject(rParent) {
	mContext = 0;
	mDevice = 0;
	mShutdown = true;
	mMajorVersion = 0;
	mMinorVersion = 0;
	mEAXSupport = false;
	mEAXVersion = 0;
}

SoundManager::~SoundManager() {
}

void SoundManager::readSettings() {
	ConfigManager* cfgManager = ConfigManager::getSingletonPtr();
	mDeviceName = cfgManager->getString("audio_device", "PulseAudio Software");
}

QStringList SoundManager::getDeviceList() {

	const ALCchar* devices = alcGetString(NULL, ALC_DEVICE_SPECIFIER );

	QStringList deviceList;

	while (*devices != '\0') {
		try {
			ALCdevice *device = alcOpenDevice(devices);

			if (device) {

				if (alcGetError(device))
					qDebug() << "Unable to open device";

				// Device seems to be valid
				ALCcontext *context = alcCreateContext(device, NULL);

				if (alcGetError(device))
					qDebug() << "Unable to create context";

				if (context) {
					// Context seems to be valid
					alcMakeContextCurrent(context);

					if (alcGetError(device))
						qDebug() << "Unable to make context current";

					deviceList.push_back(QString(alcGetString(device,
							ALC_DEVICE_SPECIFIER)));

					alcMakeContextCurrent(NULL);

					if (alcGetError(device))
						qDebug() << "Unable to clear current context";

					alcDestroyContext(context);

					if (alcGetError(device))
						qDebug() << "Unable to destroy context";
				}

				alcCloseDevice(device);

			} else {
				// There is a chance that because the device could not be
				// opened, the error flag was set, clear it.
				alcGetError(device);
			}
		} catch (...) {
			// Don't die here, we'll just skip this device.
		}

		devices += strlen(devices) + 1;
	}

	return deviceList;
}

void SoundManager::checkFeatureSupport() {

	ALenum eBufferFormat = 0;

	eBufferFormat = alGetEnumValue("AL_FORMAT_MONO16");

	if (eBufferFormat)
		mSupportedFormats[FormatData::MONO_CHANNEL] = new FormatData(
				eBufferFormat, "AL_FORMAT_MONO16", "Monophonic Sound");

	eBufferFormat = alGetEnumValue("AL_FORMAT_STEREO16");

	if (eBufferFormat)
		mSupportedFormats[FormatData::STEREO_CHANNEL] = new FormatData(
				eBufferFormat, "AL_FORMAT_STEREO16", "Stereo Sound");

	eBufferFormat = alGetEnumValue("AL_FORMAT_QUAD16");

	if (eBufferFormat)
		mSupportedFormats[FormatData::QUAD_CHANNEL] = new FormatData(
				eBufferFormat, "AL_FORMAT_QUAD16", "4 Channel Sound");

	eBufferFormat = alGetEnumValue("AL_FORMAT_51CHN16");

	if (eBufferFormat)
		mSupportedFormats[FormatData::MULTI_CHANNEL_51] = new FormatData(
				eBufferFormat, "AL_FORMAT_51CHN16", "5.1 Surround Sound");

	eBufferFormat = alGetEnumValue("AL_FORMAT_61CHN16");

	if (eBufferFormat)
		mSupportedFormats[FormatData::MULTI_CHANNEL_61] = new FormatData(
				eBufferFormat, "AL_FORMAT_61CHN16", "6.1 Surround Sound");

	eBufferFormat = alGetEnumValue("AL_FORMAT_71CHN16");

	if (eBufferFormat)
		mSupportedFormats[FormatData::MULTI_CHANNEL_71] = new FormatData(
				eBufferFormat, "AL_FORMAT_71CHN16", "7.1 Surround Sound");

	// Log supported formats
	FormatMapIterator itr = FormatMapIterator(mSupportedFormats);

	qDebug() << "Supported Formats";

	while (itr.hasNext()) {
		itr.next();
		FormatData* formatData = itr.value();
		qDebug() << '*' << formatData->getOpenAlFormat() << ','
				<< formatData->getDescription();
	}

	// Check for EAX Support
	for (int version = 5; version >= 2; version--) {
		QString versionString = QString("EAX%1.0").arg(version);
		if (alIsExtensionPresent(versionString.toAscii().data()) == AL_TRUE) {
			mEAXSupport = true;
			mEAXVersion = version;
			qDebug() << versionString << " detected";
			break;
		}
	}

	//	// Check for EFX Support
	//	if ( alcIsExtensionPresent ( mDevice, "ALC_EXT_EFX" ) == AL_TRUE ) {
	//		qDebug() << "EFX Extension Found";
	//	}
}

void SoundManager::switchMode(SoundManager::SoundMode rMode) {

	if (rMode == mMode)
		return;

	if (rMode == SoundManager::ModeNormal) {

		qDebug() << "Switching to Normal Mode";

		ALfloat listenerPos[] = { 0.0, 0.0, 4.0 };
		ALfloat listenerVel[] = { 0.0, 0.0, 0.0 };
		ALfloat listenerOri[] = { 0.0, 0.0, 1.0, 0.0, 1.0, 0.0 };
		ALfloat gain = 1.0f;

		qDebug() << "Switching to Video Playback Mode";

		alListenerfv(AL_POSITION, listenerPos);

		if (alGetError())
			qDebug() << "Failed to set Position";

		alListenerfv(AL_ORIENTATION, listenerOri);

		if (alGetError())
			qDebug() << "Failed to set Orientation";

		alListenerf(AL_GAIN, gain);

		if (alGetError())
			qDebug() << "Failed to set Gain";

		alListenerfv(AL_VELOCITY, listenerVel);

		if (alGetError())
			qDebug() << "Failed to set Velocity";

	} else if (rMode == SoundManager::ModeOpenAl) {

		qDebug() << "Switching to OpenAl Mode";

		// TODO: Attach Listner;
	} else {

		qDebug() << "Switching to Idle Mode";
	}

	mMode = rMode;
}

SoundManager::SoundMode SoundManager::getMode() const {
	return mMode;
}

void SoundManager::init() {

	if (!mShutdown)
		return;

	qDebug() << "Sound Manager Init";

	readSettings();

	alcGetIntegerv(NULL, ALC_MAJOR_VERSION, sizeof(mMajorVersion),
			&mMajorVersion);

	if (alcGetError(NULL))
		qDebug() << "Failed to retrieve version info";

	alcGetIntegerv(NULL, ALC_MINOR_VERSION, sizeof(mMinorVersion),
			&mMinorVersion);

	if (alcGetError(NULL))
		qDebug() << "Failed to retrieve version info";

	qDebug() << "OpenAL version:" << mMajorVersion << '.' << mMinorVersion;

	bool deviceInList = false;

	if (mMajorVersion >= 1 && mMinorVersion >= 1) {

		qDebug() << "Available Devices:";

		QStringList deviceList = getDeviceList();

		foreach ( QString device, deviceList )
			{
				qDebug() << device;
				deviceInList |= (device.compare(mDeviceName));
			}

		if (deviceInList) {
			qDebug() << "Prefered device available";
			mDevice = alcOpenDevice(mDeviceName.toAscii().data());
		} else {
			qDebug() << "Prefered device not available, using system default";
			mDevice = alcOpenDevice(NULL);
		}

		if (alcGetError(mDevice))
			qDebug() << "Failed to open Device";

		if (!mDevice)
			qDebug() << "Failed to open audio device";

		// Create OpenAL Context
		mContext = alcCreateContext(mDevice, NULL );

		if (alcGetError(mDevice))
			qDebug() << "Failed to create Context";

		if (!mContext)
			qDebug() << "Failed to create OpenAL Context";

		alcMakeContextCurrent(mContext);

		if (alcGetError(mDevice))
			qDebug() << "Failed to set current context";
	}

	checkFeatureSupport();

	switchMode(SoundManager::ModeIdle);
	mShutdown = false;
}

void SoundManager::shutdown() {

	if (mShutdown)
		return;

	qDebug() << "Sound Manager Shutdown";

	FormatMapIterator itr = FormatMapIterator(mSupportedFormats);

	while (itr.hasNext()) {
		itr.next();
		FormatData* formatData = itr.value();
		delete formatData;
	}

	// Release the OpenAL Context and the Audio device
	alcMakeContextCurrent(NULL);
	alcDestroyContext(mContext);
	alcCloseDevice(mDevice);

	mShutdown = true;

}

SoundManager* SoundManager::getSingletonPtr(void) {
	return ms_Singleton;
}

SoundManager& SoundManager::getSingleton(void) {
	assert( ms_Singleton );
	return (*ms_Singleton);
}
